﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SMA = System.Management.Automation;

namespace RSPowershell
{
    public class MetafilePrinter
    {
        System.Drawing.Printing.PrinterSettings m_print_settings;
        SMA.Cmdlet cmdlet;
        int cur_page_count = 0;

        public readonly List<System.Drawing.Imaging.Metafile> Metafiles = new List<System.Drawing.Imaging.Metafile>();

        PrinterPreferences printprefs;

        public MetafilePrinter(SMA.Cmdlet cmdlet, PrinterPreferences printprefs)
        {
            if (printprefs == null)
            {
                throw new System.ArgumentNullException("printprefs");
            }
            this.cmdlet = cmdlet;
            this.printprefs = printprefs;
        }

        public void PrintMetafiles()
        {
            this.BeginPrintingSession();

            // Establish print settings
            this.m_print_settings = new System.Drawing.Printing.PrinterSettings();
            this.m_print_settings.MaximumPage = this.Metafiles.Count;
            this.m_print_settings.MinimumPage = 1;
            this.m_print_settings.PrintRange = System.Drawing.Printing.PrintRange.AllPages;
            this.m_print_settings.FromPage = 1;
            this.m_print_settings.ToPage = this.Metafiles.Count;

            // setup default page settings

            var defpagesettings = this.m_print_settings.DefaultPageSettings;
            if (this.printprefs!=null)
            {
                this.cmdlet.WriteVerbose("Using PrinterPreferences to set page settings");

                this.m_print_settings.PrinterName = this.printprefs.Printer;
                this.m_print_settings.PrintFileName = this.printprefs.PrintFilename;
                if (this.m_print_settings.PrintFileName!=null)
                {
                    this.m_print_settings.PrintToFile = true;
                }
                this.cmdlet.WriteVerbose("Setting Papersize");
                defpagesettings.PaperSize = new System.Drawing.Printing.PaperSize( printprefs.PaperName, printprefs.PaperWidth100, printprefs.PaperHeight100);

                this.cmdlet.WriteVerbose("Setting Landscape");
                bool landscape_val = (this.printprefs.PageOrientation == PageOrientation.Landscape);
                defpagesettings.Landscape = landscape_val;


                var papersource = GetPaperSource(printprefs.PaperSource);
                if (papersource != null)
                {
                    this.m_print_settings.DefaultPageSettings.PaperSource = papersource;
                }
                else
                {
                    this.cmdlet.WriteVerbose("Couldn't find the desired papersource");
                    this.cmdlet.WriteVerbose("Using the printer's default");
                }

            }
            else
            {
                this.cmdlet.WriteVerbose("Using defaults for page settings");
                defpagesettings.PaperSize = new System.Drawing.Printing.PaperSize("Letter", 850, 1100);
                defpagesettings.Landscape = false;

            }

            this.cmdlet.WriteVerbose(" Landscape: {0}", defpagesettings.Landscape);
            this.cmdlet.WriteVerbose(" PaperSize: {0}", defpagesettings.PaperSize);
            this.cmdlet.WriteVerbose("PaperSource to use for this session:");
            var ps = this.m_print_settings.DefaultPageSettings.PaperSource;
            this.cmdlet.WriteVerbose(" SourceName: \"{0}\"", ps.SourceName);
            this.cmdlet.WriteVerbose(" RawKind: \"{0}\"", ps.RawKind);
            this.cmdlet.WriteVerbose(" Kind: \"{0}\"", ps.Kind);

            //p.PrinterName = ;


            // Perform the printing
            var print_document = new System.Drawing.Printing.PrintDocument();
            print_document.PrinterSettings = this.m_print_settings;
            print_document.PrintPage += new System.Drawing.Printing.PrintPageEventHandler(pd_PrintPage);
            print_document.Print();

            this.EndPrintingSession();
        }

        private System.Drawing.Printing.PaperSource GetPaperSource(string desired_papersource)
        {

            if (desired_papersource == null)
            {
                this.cmdlet.WriteVerbose("No desired papersource specified");
                this.cmdlet.WriteVerbose("Returning null - let printer pick it");
                return null;
            }

            var papersources = PrintSystem.GetPaperSources(cmdlet, this.printprefs.Printer);
            this.cmdlet.WriteVerbose("A desired papersource was given: {0}", desired_papersource);
            this.cmdlet.WriteVerbose("Searching through papersources");
            for (int i = 0; i < papersources.Count; i++)
            {
                System.Drawing.Printing.PaperSource ps = papersources[i];

                this.cmdlet.WriteVerbose("PaperSource SourceName: \"{0}\"", ps.SourceName);
                this.cmdlet.WriteVerbose("PaperSource RawKind: {0}", ps.RawKind);
                this.cmdlet.WriteVerbose("PaperSource Kind: {0}", ps.Kind);

                if (ps.SourceName == desired_papersource)
                {
                    this.cmdlet.WriteVerbose("Found a papersource with desired name");
                    return ps;
                }
            }

            this.cmdlet.WriteVerbose("Did not find a papersource with desired name");
            return null;
        }

        private void BeginPrintingSession()
        {
            this.cmdlet.WriteVerbose("Starting Printing Session");
            this.cur_page_count = 0;

            this.cmdlet.WriteVerbose(" PrintPrefs Printer: {0}", printprefs.Printer);
            this.cmdlet.WriteVerbose(" PrintPrefs PaperSource: {0}", printprefs.PaperSource);

        }

        private void EndPrintingSession()
        {
            this.cur_page_count = 0;
            this.cmdlet.WriteVerbose("Ended Printing Session");
        }

        void pd_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
        {
            this.cmdlet.WriteVerbose("Printing Current Page: {0} of {1}", this.cur_page_count + 1, this.Metafiles.Count);

            var printer_resolution = e.PageSettings.PrinterResolution;
            this.cmdlet.WriteVerbose("Printer Resolution: {0}x{1}", printer_resolution.X, printer_resolution.X);

            this.cmdlet.WriteVerbose("Retrieving Metafile");
            var metafile = this.Metafiles[this.cur_page_count];
            var metafile_header = metafile.GetMetafileHeader();

            this.cmdlet.WriteVerbose("Metafile Size: {0}", metafile.Size);
            this.cmdlet.WriteVerbose("Metafile DPI: {0}x{1}", metafile_header.DpiX, metafile_header.DpiY);
            this.cmdlet.WriteVerbose("Metafile Bounds: {0}", metafile_header.Bounds);

            // Scale the metafile so that it fits on the page

            float printing_dpi_x = 300.0f;
            float printing_dpi_y = 300.0f;

            // Account for difference of metafile dpi and printing dpi
            this.cmdlet.WriteVerbose("Scaling Metafile to adjust for DPI differences");
            float scale_x = metafile_header.DpiX / printing_dpi_x;
            float scale_y = metafile_header.DpiY / printing_dpi_y;
            this.cmdlet.WriteVerbose("Scale Transform: {0}x{0}", scale_x, scale_y);
            var mat_order = System.Drawing.Drawing2D.MatrixOrder.Prepend;
            e.Graphics.ScaleTransform(scale_x, scale_y, mat_order);

            // Scale UP to fit page
            var size1 = new System.Drawing.PointF(
                        (float)metafile.Size.Width / printing_dpi_x * 100.0f,
                        (float)metafile.Size.Height / printing_dpi_y * 100.0f);

            var size2 = new System.Drawing.PointF(
                (float) Math.Floor(e.PageSettings.PrintableArea.Width) / size1.X,
                (float) Math.Floor(e.PageSettings.PrintableArea.Height) / size1.Y );

            var scale = Math.Min(size2.X, size2.Y);
            e.Graphics.ScaleTransform(scale, scale, System.Drawing.Drawing2D.MatrixOrder.Append);



            // Draw the image
            var points = new[] {  new System.Drawing.Point(0,0) };
            var matrix = e.Graphics.Transform;
            matrix.Invert();
            matrix.TransformPoints(points);

            this.cmdlet.WriteVerbose("Drawing Metafile");
            e.Graphics.DrawImageUnscaled(metafile, points[0]);
            this.cmdlet.WriteVerbose("Finished Drawing");

            // increment page count and flag if additional pages are needed
            this.cur_page_count++;
            e.HasMorePages = this.cur_page_count < this.Metafiles.Count;
        }

    }

}